#include <QJsonObject>
#include <QJsonArray>

#include <osg/Geometry>
#include <osgUtil/SmoothingVisitor>
#include <osg/BlendFunc>

#include <TopoDS_Face.hxx>
#include <TopoDS_Wire.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <gp_Pnt.hxx>

#include "Polygon.h"
#include "JsonReader.h"
#include "GLSLPrograms.h"
#include "OsgUtil.h"
#include "OcctMisc.h"
#include "OcctMakeShape.h"

struct PolygonPrivate
{
	PolygonPrivate()
		: m_rep3D(new osg::Geometry())
	{
		m_rep3D->setNodeMask(D3SurfaceMask);
		auto ss = m_rep3D->getOrCreateStateSet();
		ss->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
		ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
		static osg::ref_ptr<osg::BlendFunc> blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		ss->setAttributeAndModes(blendFunc);
	}
	osg::ref_ptr<osg::Geometry> m_rep3D;
	std::vector <osg::Vec2d> m_points;
	bool m_selected = false;
};

Polygon::Polygon(INode* parent)
	: Ent3DFace(parent)
	, d_ptr(new PolygonPrivate)
{
}

void Polygon::setPoints(const std::vector<osg::Vec2d>& pos)
{
	if (d_ptr->m_points != pos)
	{
		d_ptr->m_points = pos;
		addFlags(UPDATE_GEOMETRY);
	}
}

const std::vector<osg::Vec2d>& Polygon::getPoints() const
{
	return d_ptr->m_points;
}

void getSectionTrianglesData(const std::vector<osg::Vec3d>& section, std::vector<osg::Vec3d>& points);

void setTrianglesNormals(osg::Geometry& geoset);
void Polygon::update3D()
{
	if (flags())
	{
		auto va = new osg::Vec3Array();
		auto m = getMatrix();
		std::vector<osg::Vec3d> trianglePoints, points;
		for (auto pt : d_ptr->m_points)
		{
			points.push_back({ pt.x(),pt.y(),0 });
		}
		getSectionTrianglesData(points, trianglePoints);
		for (auto pt : trianglePoints)
		{
			osg::Vec3d point = { pt.x(),pt.y(),0 };
			point = point * m;
			va->push_back(point);
		}
		d_ptr->m_rep3D->setVertexArray(va);

		osg::ref_ptr<osg::Vec4Array> ca = new osg::Vec4Array();

		osg::Vec4f color;
		if (d_ptr->m_selected)
			color = g_selectedFaceColor;
		else
			color = g_faceColor;
		color.a() = g_alpha;
		ca->push_back(color);

		d_ptr->m_rep3D->setColorArray(ca.get(), osg::Array::BIND_OVERALL);

		d_ptr->m_rep3D->getPrimitiveSetList().clear();
		d_ptr->m_rep3D->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, trianglePoints.size()));

		setTrianglesNormals(*d_ptr->m_rep3D);
		d_ptr->m_rep3D->dirtyGLObjects();
		removeFlags(0xFFFFFFFF);
	}
}

osg::Node * Polygon::getRep3D() const
{
	return d_ptr->m_rep3D.get();
}

void Polygon::setSelected(bool val)
{
	if (d_ptr->m_selected != val)
	{
		d_ptr->m_selected = val;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Polygon::save(QJsonObject & json) const
{
	Ent3DFace::save(json);
	Plane::save(json);

	QJsonArray points;
	for (auto pt : d_ptr->m_points)
	{
		points.append(saveVec2f(pt));
	}
	json["points"] = points;
}

void Polygon::load(const QJsonObject& json, JsonReader& reader)
{
	Ent3DFace::load(json, reader);
	Plane::load(json);

	QJsonArray pointsJson = json["points"].toArray();
	for (int i = 0; i < pointsJson.count(); i++)
	{
		auto ptJson = pointsJson.at(i);
		d_ptr->m_points.push_back(loadVec2f(ptJson.toArray()));
	}
}

std::vector<osg::Vec3d> Polygon::handles() const
{
	auto m = getMatrix();
	std::vector <osg::Vec3d> points;
	for (auto pt : d_ptr->m_points)
	{
		osg::Vec3d point = { pt.x(),pt.y(),0 };
		point = point * m;
		points.push_back(point);
	}
	return points;
}

std::vector<std::vector<osg::Vec3d>> Polygon::getEdgePoints() const
{
	std::vector<std::vector<osg::Vec3d>> result;
	auto points = handles();
	if (points.size() >= 3)
	{
		points.push_back(points.front());
		result.push_back(points);
	}
	return result;
}


void Polygon::move(const osg::Vec3d& offset)
{
	setOrigin(origin() + offset);
	addFlags(UPDATE_GEOMETRY);
}

std::shared_ptr<Ent> Polygon::clone() const
{
	auto polygon = std::make_shared<Polygon>(getParent());
	polygon->copy(*this);
	return polygon;
}

void Polygon::copy(const Ent& ent)
{
	auto polygon = dynamic_cast<const Polygon*>(&ent);
	if (polygon)
	{
		setOrigin(polygon->origin());
		setAxis(polygon->xAxis(), polygon->yAxis());
		d_ptr->m_points = polygon->getPoints();
		addFlags(UPDATE_GEOMETRY);
	}
}

void Polygon::reverse()
{
	std::reverse(d_ptr->m_points.begin(), d_ptr->m_points.end());
	addFlags(UPDATE_GEOMETRY);
}

void Polygon::updateColor()
{
	addFlags(UPDATE_GEOMETRY);
}

TopoDS_Face Polygon::shape()
{
	std::vector<osg::Vec3d> points;
	auto m = getMatrix();
	for (auto pt : d_ptr->m_points)
	{
		osg::Vec3d point = { pt.x(),pt.y(),0 };
		point = point * m;
		points.push_back(point);
	}
	int pointCount = points.size();
	double length = (points[0] - points[pointCount - 1]).length();
	if (length<1e-3)
	{
		points.pop_back();
		--pointCount;
	}
	std::vector<TopoDS_Vertex> vertice;
	for (int i = 0; i < pointCount; i++)
	{
		gp_Pnt p1(points[i].x(), points[i].y(), points[i].z());
		vertice.push_back(makeVertice(p1));
	}
	BRepBuilderAPI_MakeWire wMaker;
	for (int i = 0; i < pointCount; i++)
	{
		int j = (i + 1) % pointCount;
		BRepBuilderAPI_MakeEdge maker(vertice[i], vertice[j]);
		if (maker.IsDone())
		{
			auto edge = maker.Edge();
			fixVertexTolerance(edge);
			wMaker.Add(edge);
		}
		else
		{
			return TopoDS_Face();
		}
	}
	if (wMaker.IsDone())
	{
		TopoDS_Wire wire = wMaker.Wire();
		BRepBuilderAPI_MakeFace maker(wire, true);
		if (maker.IsDone())
		{
			return maker.Face();
		}
	}
	return TopoDS_Face();
}

void Polygon::rotate(const double angle, const osg::Vec2d & basePoint)
{
	auto m = getMatrix();
	auto r = rotate2DMatrix(basePoint.x(), basePoint.y(), angle);
	auto m2 = m * r;
	setOrigin(osg::Vec3d(0,0,0) * m2);
	osg::Vec3d xDir = osg::Matrixd::transform3x3(osg::Vec3d(1, 0, 0), m2);
	osg::Vec3d yDir = osg::Matrixd::transform3x3(osg::Vec3d(0, 1, 0), m2);
	setAxis(xDir, yDir);
}
